package com.moonshineproject.plugin.settings
{

	/**
	 *
	 * Settings Plugin - Plugin to save plugin settings
	 *
	 * Flow
	 * ---------
	 *
	 * Restoring
	 *
	 * 1)Plugins will be registered via PluginManager.registerPlugin(plug:IPlugin)
	 * 2)SettingsPlugin.initializePlugin(plug:IPlugin) will be called which reads the xml file
	 * (if any) and restores the plugin instance settings.
	 * 		A 'Proxy' we be set by ISettingsProvider.getSettings(), where each ISetting will
	 * 		either pass a string name of a public property/setter&getter
	 * 		ex : public function getSettings():Vector<ISetting>{
	 * 			return Vector.<ISetting>([
	 * 						new BooleanSetting(this,"myBooleanProperty","My Desciption")
	 * 			]);
	 * 		}
	 *
	 * 	Each render will by default have ISetting.stringValue which can take any string value,
	 * 	if the inner property value has an datatype other then String you may override this function
	 * 	to typecast to the needed property, or in use with complex objects ie. FontDescription
	 * 		ex . override public function get stringValue():String{
	 * 				var fontDescription:FontDescription = getSetting() as FontDescription;
	 * 				return [fontDescription.fontName,........,......,...].join(",");
	 * 			}
	 * 			override public function set stringValue(value:String):void{
	 * 				// Construct new FontDescription
	 * 				var args:Array = value.split(",");
	 * 				var fontDescription:FontDescription = new FontDescription(args[0],args[1],args[2],args[3]);
	 * 				applySetting(fontDescription);
	 * 			}
	 * 3) After SettingPlugin.readClassSettings(IPlugin) is called a Boolean value of true is returned
	 * 		as if the plugin settings says to activate the plugin , by default (on first plugin run) all will be
	 * 		activated.
	 *
	 *
	 * Note: Plugin Settings will be stored in File.applicationStorageDirectory+/settings in the following format
	 * 		CRC32(QualifiedClassName)_CLASSNAME.xml
	 * 		This is done to ensure that no two settings ( even if CLASSNAME is the same) will
	 * 		have the same settings file
	 *
	 * 4) IPlugin.activatedByDefault ensures if the plugin will be activated by default
	 * 		when the plugin is loaded for the first time (without settings xml file written)
	 *
	 * */
	import com.moonshineproject.GlobalEventDispatcher;
	import com.moonshineproject.IDEModel;
	import com.moonshineproject.plugin.IPlugin;
	import com.moonshineproject.plugin.PluginBase;
	import com.moonshineproject.plugin.PluginManager;
	import com.moonshineproject.plugin.settings.vo.ISetting;
	import com.moonshineproject.plugin.settings.vo.PluginSetting;
	import com.moonshineproject.plugin.settings.vo.PluginSettingsWrapper;
	import com.moonshineproject.utils.moonshine_internal;

	import flash.events.Event;
	import flash.filesystem.File;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	import flash.utils.ByteArray;
	import flash.utils.getQualifiedClassName;

	import nochump.util.zip.CRC32;




	public class SettingsPlugin extends PluginBase implements ISettingsProvider
	{
		override public function get name():String
		{
			return "Project Settings Plugin";
		}

		override public function get author():String
		{
			return "Moonshine Project Team";
		}

		override public function get description():String
		{
			return "Provides settings for all plugins.";
		}

		public function getSettingsList():Vector.<ISetting>
		{
			return new Vector.<ISetting>();
		}


		public static const EVENT_OPEN_SETTINGS:String = "openSettingsEvent";


		private var model:IDEModel = IDEModel.getInstance();

		public var pluginManager:PluginManager;

		private var settingsDirectory:File

		private var crc32:CRC32 = new CRC32();
		private var settingsOpen:Boolean;

		// local bytearray , may help with recreating in a loop????
		private var byteArray:ByteArray = new ByteArray();


		public function SettingsPlugin()
		{

			dispatcher.addEventListener(EVENT_OPEN_SETTINGS, openAppSettings);
			settingsDirectory = File.applicationStorageDirectory.resolvePath("settings");


			if (!settingsDirectory.exists)
				settingsDirectory.createDirectory();
		}

		public function initializePlugin(plug:IPlugin):Boolean
		{
			if (!plug)
				return false;


			var activated:Boolean = readClassSettings(plug);

			if (activated)
				plug.activate();

			return activated;

		}

		private function openAppSettings(event:Event):void
		{
			if (settingsOpen)
				return;
			var settings:SettingsView = new SettingsView();

			var catPlugins:String = "Plugins";
			settings.addCategory(catPlugins);

			use namespace moonshine_internal;
			var plugins:Vector.<IPlugin> = pluginManager.getPlugins();

			var qualifiedClassName:String;
			var provider:ISettingsProvider
			for each (var plug:IPlugin in plugins)
			{
				if (plug == this)
					continue; // omit Settings Plugin from showing up
				provider = plug as ISettingsProvider;

				qualifiedClassName = getQualifiedClassName(plug);

				var setList:Vector.<ISetting> = provider ? provider.getSettingsList() : new Vector.<ISetting>();

				var p:PluginSetting = new PluginSetting(plug.name, plug.author, plug.description, plug.activated);
				setList.unshift(p);

				var settingsObject:IHasSettings = new PluginSettingsWrapper(plug.name, setList, qualifiedClassName);
				if (settingsObject)
				{
					settings.addSetting(settingsObject, catPlugins);
				}

			}

			model.editors.addItem(settings);

			settings.addEventListener(SettingsView.EVENT_SAVE, handleSettingsSaved, false, 0, true);
			//settingsOpen = true;
		}

		private function handleSettingsSaved(e:Event):void
		{
			var settings:SettingsView = e.target as SettingsView;
			if (!settings)
				return;
			settings.removeEventListener(SettingsView.EVENT_SAVE, handleSettingsSaved);

			var catPlugins:String = "Plugins";
			var allSettings:Array = settings.getSettings(catPlugins);

			for each (var settingObject:IHasSettings in allSettings)
			{
				saveClassSettings(settingObject);
			}
			settingsOpen = false;

		}

		private function generateUniqueID(className:String):uint
		{
			crc32.reset();
			byteArray.clear();
			byteArray.writeUTF(className);
			crc32.update(byteArray);
			return crc32.getValue();
		}

		private function getSaveDataFromList(settingsList:Vector.<ISetting>):XML
		{
			var saveData:XML = <settings>
					<properties></properties>
				</settings>
				;
			var settingsClassName:String;
			var propName:String;
			var propValue:String;
			for each (var setting:ISetting in settingsList)
			{
				propName = (setting is PluginSetting) ? "activated" : setting.name;
				propValue = setting.stringValue;
				saveData.properties.appendChild(<{propName}>{propValue}</{propName}>
					);

			}


			return saveData;
		}


		public function readClassSettings(plug:IPlugin):Boolean
		{
			var provider:ISettingsProvider = plug as ISettingsProvider;
			var settingsFile:File = generateSettingsPath(getQualifiedClassName(plug))
			if (!settingsFile.exists)
				return plug.activatedByDefault;

			var stream:FileStream = new FileStream();
			stream.open(settingsFile, FileMode.READ);


			var saveData:XML = new XML(stream.readUTFBytes(stream.bytesAvailable));

			stream.close();

			var settingsList:Vector.<ISetting> = provider ? provider.getSettingsList() : new Vector.<ISetting>();
			var propName:String

			for each (var setting:ISetting in settingsList)
			{
				propName = setting.name;
				if (!saveData.properties.hasOwnProperty(propName))
					continue;

				setting.stringValue = String(saveData.properties[propName].text());
				setting.commitChanges();
			}

			return (saveData.properties.hasOwnProperty("activated") &&
				String(saveData.properties["activated"].text()) == "false") ? false : true;
		}

		public function saveClassSettings(wrapper:IHasSettings):Boolean
		{
			if (!wrapper)
				return true;

			//if( !wrapper.hasChanged())return true;

			var settingsList:Vector.<ISetting> = wrapper.getSettingsList();

			var saveData:XML = getSaveDataFromList(settingsList);
			if (!saveData.length())
				return true;

			var qualifiedClassName:String = (wrapper as PluginSettingsWrapper).qualifiedClassName;

			var settingsFile:File = generateSettingsPath(qualifiedClassName);
			var className:String = qualifiedClassName.split("::").pop();
			use namespace moonshine_internal;
			var plug:IPlugin = pluginManager.getPluginByClassName(className);
			if (plug)
			{
				// Check to see what the current state of the plugin is
				var activated:Boolean = String(saveData.properties.activated.text()) == "true";
				if (plug.activated && !activated)
				{
					plug.deactivate();
				}
				else if (!plug.activated && activated)
				{
					plug.activate();
				}


			}
			var stream:FileStream = new FileStream();
			stream.open(settingsFile, FileMode.WRITE);
			stream.writeUTFBytes(saveData.toXMLString());
			stream.close();

			return settingsFile.size > 0;
		}

		private function generateSettingsPath(qualifiedClassName:String):File
		{
			var uniqueID:uint = generateUniqueID(qualifiedClassName);
			var realClassName:String = qualifiedClassName.split("::").pop();
			return settingsDirectory.resolvePath(uniqueID + "_" + realClassName + ".xml");
		}


	}
}